/*
 *  Copyright 2014 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

package com.cloudptt.api.pjsip.service;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import com.cloudptt.api.pjsip.utils.Ringer;
import com.cloudptt.api.product.device.DeviceInfo;
import com.cloudptt.api.product.func.call.CallAttr;
import com.cloudptt.api.product.log.Log;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.provider.Settings;

/**
 * AppRTCAudioManager manages all audio related parts of the AppRTC demo.
 */
public class AppRTCAudioManager implements OnAudioFocusChangeListener {
	private static final String TAG = "AppRTCAudioManager";
	public static final int Vol_Type_Up = 1;
	public static final int Vol_Type_Down = 2;

	/**
	 * AudioDevice is the names of possible audio devices that we currently
	 * support.
	 */
	// TODO(henrika): add support for BLUETOOTH as well.
	public enum AudioDevice {
		SPEAKER_PHONE,
		WIRED_HEADSET,
		EARPIECE,
	}

	private final Context apprtcContext;
	//  private final Runnable onStateChangeListener;
	private boolean initialized = false;
	private AudioManager audioManager;
	private int savedAudioMode = AudioManager.MODE_INVALID;
	private boolean savedIsSpeakerPhoneOn = false;
	private boolean savedIsMicrophoneMute = false;

	// For now; always use the speaker phone as default device selection when
	// there is a choice between SPEAKER_PHONE and EARPIECE.
	// TODO(henrika): it is possible that EARPIECE should be preferred in some
	// cases. If so, we should set this value at construction instead.
	private final AudioDevice defaultAudioDevice = AudioDevice.SPEAKER_PHONE;

	// Proximity sensor object. It measures the proximity of an object in cm
	// relative to the view screen of a device and can therefore be used to
	// assist device switching (close to ear <=> use headset earpiece if
	// available, far from ear <=> use speaker phone).
	//  private AppRTCProximitySensor proximitySensor = null;

	// Contains the currently selected audio device.
	private AudioDevice selectedAudioDevice;

	// Contains a list of available audio devices. A Set collection is used to
	// avoid duplicate elements.
	private final Set<AudioDevice> audioDevices = new HashSet<AudioDevice>();

	// Broadcast receiver for wired headset intent broadcasts.
	//private BroadcastReceiver wiredHeadsetReceiver;

	private Ringer ringer;

//	private int callType = CallAttr.CallType_Single;

	// This method is called when the proximity sensor reports a state change,
	// e.g. from "NEAR to FAR" or from "FAR to NEAR".
	private void onProximitySensorChangedState() {
		// The proximity sensor should only be activated when there are exactly two
		// available audio devices.
		if (audioDevices.size() == 2
				&& audioDevices.contains(AppRTCAudioManager.AudioDevice.EARPIECE)
				&& audioDevices.contains(
						AppRTCAudioManager.AudioDevice.SPEAKER_PHONE)) {
			//      if (proximitySensor.sensorReportsNearState()) {
			//        // Sensor reports that a "handset is being held up to a person's ear",
			//        // or "something is covering the light sensor".
			//        setAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE);
			//      } else 
			{
				// Sensor reports that a "handset is removed from a person's ear", or
				// "the light sensor is no longer covered".
				setAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE);
			}
		}
	}

	/** Construction */
	public static AppRTCAudioManager create(Context context) {
		return new AppRTCAudioManager(context);
	}

	private AppRTCAudioManager(Context context) {
		apprtcContext = context;
		//    onStateChangeListener = deviceStateChangeListener;
		audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
		ringer = new Ringer(context);
		// Create and initialize the proximity sensor.
		// Tablet devices (e.g. Nexus 7) does not support proximity sensors.
		// Note that, the sensor will not be active until start() has been called.
		//    proximitySensor = AppRTCProximitySensor.create(context, new Runnable() {
		//      // This method will be called each time a state change is detected.
		//      // Example: user holds his hand over the device (closer than ~5 cm),
		//      // or removes his hand from the device.
		//      public void run() {
		//        onProximitySensorChangedState();
		//      }
		//    });
		AppRTCUtils.logDeviceInfo(TAG);
	}


	synchronized public void startRing() {
		Log.v(TAG, "startRing ringer.isRinging()=" + ringer.isRinging() );
		if(!ringer.isRinging()) {
			ringer.ring(Ringer.PROGRESS_RING,getRingtone());
		}else {
			Log.v(TAG, "Already ringing ....");
		}
	}

	public String getRingtone() {
		String ringtone = Settings.System.DEFAULT_RINGTONE_URI.toString();
		return ringtone;
	}

	synchronized public void startNotificationtone() {
		Log.v(TAG, "startNotificationtone ringer.isRinging()=" + ringer.isRinging() );
		if(!ringer.isRinging()) {
			ringer.ring(Ringer.PROGRESS_NOTIFY,getNotificationtone());
		}else {
			Log.v(TAG, "Already ringing ....");
		}
	}

	public String getNotificationtone() {
		String ringtone = Settings.System.DEFAULT_NOTIFICATION_URI.toString();
		return ringtone;
	}

	/**
	 * Stop all ringing. <br/>
	 * Warning, this will not unfocus audio.
	 */
	synchronized public void stopRing() {
		Log.v(TAG, "stopRing():ringer.isRinging()=" + ringer.isRinging() );
		if(ringer.isRinging()) {
			ringer.stopRing();
		}
	}

	public void init() {
		Log.d(TAG, "init");
		if (initialized) {
			return;
		}

		// Store current audio state so we can restore it when close() is called.
		savedAudioMode = audioManager.getMode();
		savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
		savedIsMicrophoneMute = audioManager.isMicrophoneMute();

		int streamType = DeviceInfo.getInfo().getStreamType();
		// Request audio focus before making any device switch.
		int isRequested = audioManager.requestAudioFocus(this, streamType,AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
		int yourVolume = audioManager.getStreamMaxVolume(streamType);
		audioManager.setStreamVolume(streamType, yourVolume, 0);
		updateAudioChannel(CallAttr.CallType_Group);

		registerForWiredHeadsetIntentBroadcast();

		initialized = true;
	}


	public void updateAudioChannel(int callType) {
//		BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//		int state = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET);
//		Log.d(TAG, "AppRTCAudioManager setAudioChannel state=" + state);
//		if (BluetoothProfile.STATE_CONNECTED == state) {
////			enableBTSco(true);
//			if(callType == CallAttr.CallType_Single)
//			{
//				int mode = DeviceInfo.getInfo().getPrivateCallMode();
//				audioManager.setMode(mode);
//			}
//			else
//			{
//				int mode = DeviceInfo.getInfo().getOtherCallMode();
//				audioManager.setMode(mode);
//			}
//			audioManager.setSpeakerphoneOn(false);
//		}
//		else
		{
//			enableBTSco(false);
			if(callType == CallAttr.CallType_Single)
			{
				int mode = DeviceInfo.getInfo().getPrivateCallMode();
				audioManager.setMode(mode);
				audioManager.setSpeakerphoneOn(false);
			}
			else
			{
				int mode = DeviceInfo.getInfo().getOtherCallMode();
				Log.d(TAG, "setAudioDevice mode = " + mode);
				audioManager.setSpeakerphoneOn(true);
				audioManager.setParameters("poc_status=halfduplex");
				Log.d(TAG, "setAudioDevice END");
			}

			// Always disable microphone mute during a WebRTC call.
			setMicrophoneMute(false);
			updateAudioDeviceState(hasWiredHeadset());
		}
	}

//	private void enableBTSco(boolean isSco) {
//		if (isSco) {
//			audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
//			audioManager.startBluetoothSco();
//			audioManager.setBluetoothScoOn(true);
//		} else {
//			audioManager.stopBluetoothSco();
//			audioManager.setBluetoothScoOn(false);
//		}
//	}

	public void close() {
		Log.d(TAG, "close");
		if (!initialized) {
			return;
		}

		unregisterForWiredHeadsetIntentBroadcast();
//		enableBTSco(false);
		// Restore previously stored audio states.
		setSpeakerphoneOn(savedIsSpeakerPhoneOn);
		setMicrophoneMute(savedIsMicrophoneMute);
		audioManager.setMode(savedAudioMode);
		audioManager.setParameters("poc_status=off");
		audioManager.abandonAudioFocus(null);

		//    if (proximitySensor != null) {
		//      proximitySensor.stop();
		//      proximitySensor = null;
		//    }
		initialized = false;
	}

	/** Changes selection of the currently active audio device. */
	public void setAudioDevice(AudioDevice device) {
		Log.d(TAG, "setAudioDevice(device=" + device + ")");
		AppRTCUtils.assertIsTrue(audioDevices.contains(device));

		switch (device) {
		case SPEAKER_PHONE:
			setSpeakerphoneOn(true);
			selectedAudioDevice = AudioDevice.SPEAKER_PHONE;
			break;
		case EARPIECE:
			setSpeakerphoneOn(false);
			selectedAudioDevice = AudioDevice.EARPIECE;
			break;
		case WIRED_HEADSET:
			setSpeakerphoneOn(false);
			selectedAudioDevice = AudioDevice.WIRED_HEADSET;
			break;
		default:
			Log.d(TAG, "Invalid audio device selection");
			break;
		}
		onAudioManagerChangedState();
	}

	/** Returns current set of available/selectable audio devices. */
	public Set<AudioDevice> getAudioDevices() {
		return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices));
	}

	/** Returns the currently selected audio device. */
	public AudioDevice getSelectedAudioDevice() {
		return selectedAudioDevice;
	}

	/**
	 * Registers receiver for the broadcasted intent when a wired headset is
	 * plugged in or unplugged. The received intent will have an extra
	 * 'state' value where 0 means unplugged, and 1 means plugged.
	 */
	private void registerForWiredHeadsetIntentBroadcast() {
		IntentFilter filter = new IntentFilter();
		filter.addAction(Intent.ACTION_HEADSET_PLUG);
		filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
		filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
		filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
		filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
		filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
		apprtcContext.registerReceiver(wiredHeadsetReceiver, filter);
	}

	/** Receiver which handles changes in wired headset availability. */
	BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
		private static final int STATE_UNPLUGGED = 0;
		private static final int STATE_PLUGGED = 1;
		private static final int HAS_NO_MIC = 0;
		private static final int HAS_MIC = 1;

		@Override
		public void onReceive(Context context, Intent intent) {

			String action = intent.getAction();
			Log.i(TAG, " wiredHeadsetReceiver action=" +action);
			if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
				int state = intent.getIntExtra("state", STATE_UNPLUGGED);
				int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
				String name = intent.getStringExtra("name");

				boolean hasWiredHeadset = (state == STATE_PLUGGED) ? true : false;
				switch (state) {
				case STATE_UNPLUGGED:
					updateAudioDeviceState(hasWiredHeadset);
					break;
				case STATE_PLUGGED:
					if (selectedAudioDevice != AudioDevice.WIRED_HEADSET) {
						updateAudioDeviceState(hasWiredHeadset);
					}
					break;
				default:
					Log.d(TAG, "Invalid state");
					break;
				}
			} else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)||BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)||BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
				//setAudioChannel();
			}
			else if (AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED.equals(action))
			{
				
			}
			else if (AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED.equals(action))
			{
				int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
				int val = AudioManager.SCO_AUDIO_STATE_CONNECTED;
				Log.i(TAG, "onReceive ACTION_SCO_AUDIO_STATE_UPDATED state=" +state);
			}
		}
	};

	/** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */
	private void unregisterForWiredHeadsetIntentBroadcast() {
		if(apprtcContext != null
				&& wiredHeadsetReceiver != null)
		{
			apprtcContext.unregisterReceiver(wiredHeadsetReceiver);
		}
		wiredHeadsetReceiver = null;
	}

	/** Sets the speaker phone mode. */
	public void setSpeakerphoneOn(boolean on) {
		boolean wasOn = audioManager.isSpeakerphoneOn();
		if (wasOn == on) {
			return;
		}
		audioManager.setSpeakerphoneOn(on);
	}

	/** Sets the microphone mute state. */
	private void setMicrophoneMute(boolean on) {
		boolean wasMuted = audioManager.isMicrophoneMute();
		if (wasMuted == on) {
			return;
		}
		audioManager.setMicrophoneMute(on);
	}

	/** Gets the current earpiece state. */
	private boolean hasEarpiece() {
		return apprtcContext.getPackageManager().hasSystemFeature(
				PackageManager.FEATURE_TELEPHONY);
	}

	/**
	 * Checks whether a wired headset is connected or not.
	 * This is not a valid indication that audio playback is actually over
	 * the wired headset as audio routing depends on other conditions. We
	 * only use it as an early indicator (during initialization) of an attached
	 * wired headset.
	 */
	@Deprecated
	private boolean hasWiredHeadset() {
		return audioManager.isWiredHeadsetOn();
	}

	/** Update list of possible audio devices and make new device selection. */
	private void updateAudioDeviceState(boolean hasWiredHeadset) {
		// Update the list of available audio devices.
		audioDevices.clear();
		if (hasWiredHeadset) {
			// If a wired headset is connected, then it is the only possible option.
			audioDevices.add(AudioDevice.WIRED_HEADSET);
		} else {
			// No wired headset, hence the audio-device list can contain speaker
			// phone (on a tablet), or speaker phone and earpiece (on mobile phone).
			audioDevices.add(AudioDevice.SPEAKER_PHONE);
			if (hasEarpiece())  {
				audioDevices.add(AudioDevice.EARPIECE);
			}
		}
		Log.d(TAG, "audioDevices: " + audioDevices);

		// Switch to correct audio device given the list of available audio devices.
		if (hasWiredHeadset) {
			setAudioDevice(AudioDevice.WIRED_HEADSET);
		} else {
			setAudioDevice(defaultAudioDevice);
		}
	}

	/** Called each time a new audio device has been added or removed. */
	private void onAudioManagerChangedState() {
		Log.d(TAG, "onAudioManagerChangedState: devices=" + audioDevices
				+ ", selected=" + selectedAudioDevice);

		// Enable the proximity sensor if there are two available audio devices
		// in the list. Given the current implementation, we know that the choice
		// will then be between EARPIECE and SPEAKER_PHONE.
		if (audioDevices.size() == 2) {
			AppRTCUtils.assertIsTrue(audioDevices.contains(AudioDevice.EARPIECE)
					&& audioDevices.contains(AudioDevice.SPEAKER_PHONE));
			// Start the proximity sensor.
			//      proximitySensor.start();
		} else if (audioDevices.size() == 1) {
			// Stop the proximity sensor since it is no longer needed.
			//      proximitySensor.stop();
		} else {
			Log.d(TAG, "Invalid device list");
		}

		//    if (onStateChangeListener != null) {
		//      // Run callback to notify a listening client. The client can then
		//      // use public getters to query the new state.
		//      onStateChangeListener.run();
		//    }
	}

//	public void requestAudioFocus()
//	{
//		if(audioManager != null)
//		{
//			int isRequested = audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL,
//					AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
//			Log.d(TAG, "requestAudioFocus isRequested = " + isRequested);
//		}
//	}
//
//	public void releaseAudioFocus()
//	{
//		if(audioManager != null)
//		{
//			int isRequested = audioManager.abandonAudioFocus(this);
//			Log.d(TAG, "releaseAudioFocus isRequested = " + isRequested);
//		}
//	}
	
	public void onVol(int type)
	{
		Log.d(TAG, "onVolDown type = " + type);
		if(audioManager != null)
		{
			if(Vol_Type_Up == type)
			{
				audioManager.adjustStreamVolume(AudioManager.STREAM_VOICE_CALL, AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
			}
			else if(Vol_Type_Down == type)
			{
				audioManager.adjustStreamVolume(AudioManager.STREAM_VOICE_CALL, AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI);
			}
		}
	}

	@Override
	public void onAudioFocusChange(int focusChange)
	{
		// TODO Auto-generated method stub
		Log.d(TAG, "onAudioFocusChange focusChange = " + focusChange + " AUDIOFOCUS_LOSS = " + AudioManager.AUDIOFOCUS_LOSS);
		Log.d(TAG, "onAudioFocusChange AUDIOFOCUS_GAIN = " + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK + " AUDIOFOCUS_LOSS = " + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
		if(AudioManager.AUDIOFOCUS_LOSS == focusChange)
		{
			this.audioManager.abandonAudioFocus(this);
		}
	}
}
